package com.wsc.personalblog.service.impl;

import com.wsc.personalblog.DTO.RoleDTO;
import com.wsc.personalblog.DTO.UserDTO;
import com.wsc.personalblog.entity.Role;
import com.wsc.personalblog.entity.UserExample;
import com.wsc.personalblog.mapper.RoleMapper;
import com.wsc.personalblog.service.RoleService;
import com.wsc.personalblog.utils.AliYunOssUtil;
import com.wsc.personalblog.common.Constant;
import com.wsc.personalblog.entity.User;
import com.wsc.personalblog.mapper.UserMapper;
import com.wsc.personalblog.security.SecurityUser;
import com.wsc.personalblog.service.UserService;
import com.wsc.personalblog.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class UserServiceImpl implements UserService {


    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RoleMapper roleMapper;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;
    @Autowired
    private MailService mailService;

    @Override
    public UserDTO backendLogin(User user) {
        // 后台登录需要判断当前登录用户是否具有BOSS或者admin角色
        UsernamePasswordAuthenticationToken authenticationToken;
        String username = user.getUsername();
        String userQqMailbox = user.getUserQqMailbox();
        if (username != null && username.length() != 0) {
            // 检查用户名是否包含不合法字符
            if(!checkUsername(username))
                throw new RuntimeException(
                        "不能包含" + Constant.USERNAME + "或者" + Constant.USER_QQ_MAILBOX + "字眼");
            authenticationToken = new UsernamePasswordAuthenticationToken(Constant.USERNAME + username, user.getPassword());
        } else if (userQqMailbox != null && userQqMailbox.length() != 0) {
            // 检查用户名是否包含不合法字符
            if(!checkUsername(userQqMailbox))
                throw new RuntimeException(
                        "不能包含" + Constant.USERNAME + "或者" + Constant.USER_QQ_MAILBOX + "字眼");
            authenticationToken = new UsernamePasswordAuthenticationToken(Constant.USER_QQ_MAILBOX + userQqMailbox, user.getPassword());
        } else throw new RuntimeException("请输入你的用户名或者邮箱号");
        // 认证
        Authentication authenticate = null;
        try{
            authenticate = authenticationManager.authenticate(authenticationToken);
        }catch (AuthenticationException e){
            e.printStackTrace();
            throw new RuntimeException("认证失败");
        }
        if (Objects.isNull(authenticate)) {
            throw new RuntimeException("用户名或密码错误");
        }
        // 检查登录用户的角色信息
        int count = 0;
        SecurityUser securityUser = (SecurityUser) authenticate.getPrincipal();
        Collection<? extends GrantedAuthority> authorities = securityUser.getAuthorities();
        for (GrantedAuthority authority : authorities) {
            String authority1 = authority.getAuthority();
            if(authority1.equals(Constant.ROLE_BOSS) ||
                    authority1.equals(Constant.ROLE_ADMIN)){
                count++;
                break;
            }
        }
        if(count == 0) throw new RuntimeException("你不具备进入后台系统的权限");
        // 登录成功，并且拥有权限后的处理
        //使用userid生成token
        User user1 = securityUser.getUser();
        String userId = user1.getId().toString();
        String jwt = JwtUtil.createJWT(userId);
        // 将用户信息SecurityUser存入redis中
        String key = Constant.BOSS_LOGIN + userId;
        redisTemplate.opsForValue().set(key, securityUser,1, TimeUnit.DAYS);
        UserDTO res = new UserDTO(user1.getId(), user1.getUsername(), null, null, jwt, user1.getUserAvatar(), user1.getUserQqMailbox());
        //把token响应给前端
        return res;
    }

    @Override
    public UserDTO login(User user) {

        UsernamePasswordAuthenticationToken authenticationToken;
        String username = user.getUsername();
        String userQqMailbox = user.getUserQqMailbox();
        if (username != null && username.length() != 0) {
            // 检查用户名是否包含不合法字符
            if(!checkUsername(username))
                throw new RuntimeException(
                        "不能包含" + Constant.USERNAME + "或者" + Constant.USER_QQ_MAILBOX + "字眼");
            authenticationToken = new UsernamePasswordAuthenticationToken(Constant.USERNAME + username, user.getPassword());
        } else if (userQqMailbox != null && userQqMailbox.length() != 0) {
            // 检查用户名是否包含不合法字符
            if(!checkUsername(userQqMailbox))
                throw new RuntimeException(
                        "不能包含" + Constant.USERNAME + "或者" + Constant.USER_QQ_MAILBOX + "字眼");
            authenticationToken = new UsernamePasswordAuthenticationToken(Constant.USER_QQ_MAILBOX + userQqMailbox, user.getPassword());
        } else throw new RuntimeException("请输入你的用户名或者邮箱号");
        // 认证
        Authentication authenticate = null;
        try{
            authenticate = authenticationManager.authenticate(authenticationToken);
        }catch (AuthenticationException e){
            e.printStackTrace();
            throw new RuntimeException("认证失败");
        }
        if (Objects.isNull(authenticate)) {
            throw new RuntimeException("用户名或密码错误");
        }
        //使用userid生成token
        SecurityUser securityUser = (SecurityUser) authenticate.getPrincipal();
        User user1 = securityUser.getUser();
        String userId = user1.getId().toString();
        String jwt = JwtUtil.createJWT(userId);
        // 将用户信息SecurityUser存入redis中
        String key = Constant.LOGIN + userId;
        redisTemplate.opsForValue().set(key, securityUser,1, TimeUnit.DAYS);
        UserDTO res = new UserDTO(user1.getId(), user1.getUsername(), null, null, jwt, user1.getUserAvatar(), user1.getUserQqMailbox());
        //把token响应给前端
        return res;
    }

    @Override
    public UserDTO autoLogin(String token) {
        String userid = parse(token);
        String redisKey = Constant.LOGIN + userid;
        SecurityUser securityUser = getSecurityUser();
        User user = securityUser.getUser();
        UserDTO res = new UserDTO(user.getId(), user.getUsername(), null, null, null, user.getUserAvatar(), user.getUserQqMailbox());
        //把token响应给前端
        return res;
    }

    @Override
    public void register(UserDTO user) {
        // 检查验证码是否正确
        String code = user.getCode();
        String userQqMailbox = user.getUserQqMailbox();
        boolean b = mailService.checkVCode(userQqMailbox, code);
        if (!b) throw new RuntimeException("验证码无效");
        // 检查用户名是否合法
        if(checkUsername(user.getUsername()))
            throw new RuntimeException(
                "不能包含" + Constant.USERNAME + "或者" + Constant.USER_QQ_MAILBOX + "字眼！你的验证码还可以继续使用。");
        user.setCreateDate(LocalDateTime.now());
        user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
        try {
            userMapper.insertSelective(user);
            // 验证成功并且注册，清除验证码。
            redisTemplate.delete(Constant.VERIFICATION_CODE + userQqMailbox);
            log.info(user.getUsername() + "\t完成注册");
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("用户名已被占用，你的验证码还可以继续使用。");
        }
    }

    @Override
    public User getUserAndRoleInfoByUsername(String username) {
        return userMapper.getUserAndRoleInfoByUsername(username);
    }

    @Override
    public void getVerificationCode(String mail) {
        User u = userMapper.getUserAndRoleInfoByMail(mail);
        if(u != null) throw new RuntimeException("验证码获取失败，该邮箱已被注册,请直接登录");
        mailService.sendMimeMail(mail);
    }

    @Override
    public User getUserAndRoleInfoByMail(String username) {
        return userMapper.getUserAndRoleInfoByMail(username);
    }

    @Override
    public void getLoginVerificationCode(String mail1) {
        mailService.sendMimeMail(mail1);
    }

    @Override
    public UserDTO backendEmailLogin(UserDTO user) {
        // 后台登录需要判断当前登录用户是否具有BOSS或者admin角色
        String userQqMailbox = user.getUserQqMailbox();
        String code = user.getCode();
        // 检查当前邮箱是否已注册，如果未注册，则返回错误信息
        User u = userMapper.getUserAndRoleInfoByMail(userQqMailbox);
        if(u == null) throw new RuntimeException("邮箱未被注册，禁止进入后台管理系统");
        // 检查验证码
        boolean b = mailService.checkVCode(userQqMailbox, code);
        if(!b) throw new RuntimeException("验证码无效");
        UsernamePasswordAuthenticationToken authenticationToken;
        authenticationToken = new UsernamePasswordAuthenticationToken(
                Constant.USER_QQ_MAILBOX_V_CODE + userQqMailbox, code);
        // 认证
        Authentication authenticate = null;
        try{
            authenticate = authenticationManager.authenticate(authenticationToken);
        }catch (AuthenticationException e){
            e.printStackTrace();
            throw new RuntimeException("认证失败");
        }
        if (Objects.isNull(authenticate)) {
            throw new RuntimeException("用户名或密码错误");
        }
        // 认证成功
        log.info("验证成功，开始清除验证码");
        redisTemplate.delete(Constant.VERIFICATION_CODE+userQqMailbox);
        log.info("清除完毕");
        // 检查角色信息
        int count = 0;
        SecurityUser securityUser = (SecurityUser) authenticate.getPrincipal();
        Collection<? extends GrantedAuthority> authorities = securityUser.getAuthorities();
        for (GrantedAuthority authority : authorities) {
            String authority1 = authority.getAuthority();
            if(authority1.equals(Constant.ROLE_BOSS) ||
                    authority1.equals(Constant.ROLE_ADMIN)){
                count++;
                break;
            }
        }
        if(count == 0) throw new RuntimeException("你不具备进入后台系统的权限");
        // 登录成功，并且拥有权限后的处理
        // 使用userid生成token
        User user1 = securityUser.getUser();
        String userId = user1.getId().toString();
        String jwt = JwtUtil.createJWT(userId);
        // 将用户信息SecurityUser存入redis中
        String key = Constant.BOSS_LOGIN + userId;
        redisTemplate.opsForValue().set(key, securityUser,1, TimeUnit.DAYS);
        UserDTO res = new UserDTO(user1.getId(), user1.getUsername(), null, null, jwt, user1.getUserAvatar(), user1.getUserQqMailbox());
        //把token响应给前端
        return res;
    }

    private String parse(String token){
        if (!StringUtils.hasText(token)) {
            throw new RuntimeException("没有token");
        }
        //解析token
        try {
            Claims claims = JwtUtil.parseJWT(token);
            return claims.getSubject();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("token非法");
        }
    }

    private SecurityUser getSecurityUser(){
        // 拿到用户信息
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        return (SecurityUser) authentication.getPrincipal();
    }

    @Override
    public UserDTO backendAutoLogin(String token) {
        String userid = parse(token);
        String redisKey = Constant.BOSS_LOGIN + userid;
        SecurityUser securityUser = getSecurityUser();
        User user = securityUser.getUser();
        UserDTO res = new UserDTO(user.getId(), user.getUsername(), null, null, token, user.getUserAvatar(), user.getUserQqMailbox());
        //把token响应给前端
        return res;
    }

    @Override
    public List<User> getUserInfoList() {
        List<User> users = userMapper.selectByExample(null);
        return users;
    }

    @Override
    public Integer addUser(User user) {
        // 检查用户名是否合法
        if(checkUsername(user.getUsername()))
            throw new RuntimeException(
                    "不能包含" + Constant.USERNAME + "或者" + Constant.USER_QQ_MAILBOX + "字眼！你的验证码还可以继续使用。");
        user.setCreateDate(LocalDateTime.now());
        String password = user.getPassword();
        String encode = bCryptPasswordEncoder.encode(password);
        user.setPassword(encode);
        try {
            int id = userMapper.insertSelective(user);
            return id;
        }catch (Exception e){
            throw new RuntimeException("用户名已存在");
        }

    }

    @Override
    @Transactional
    public void removeUser(Integer id) {
        // 删除角色
        userMapper.removeRole(id);
        userMapper.deleteByPrimaryKey(id);
    }

    @Override
    public void updateUser(User user) {
        String password = user.getPassword().trim();
        if(password == null || password.length()==0) throw new RuntimeException("请输入正确的密码");
        String encode = bCryptPasswordEncoder.encode(password);
        user.setPassword(encode);
        userMapper.updateByPrimaryKeySelective(user);
    }

    @Override
    public RoleDTO getRole(Integer userId) {
        List<Role> roles = roleMapper.selectByExample(null);
        List<Integer> roleIDList = roleMapper.getRoleIdByUserId(userId);
        return new RoleDTO(roles,roleIDList);
    }

    @Override
    @Transactional
    public void updateRole(RoleDTO roleDTO) {
        // 删除旧信息角色
        Integer userId = roleDTO.getUserId();
        roleMapper.unbindRoleByUserId(userId);
        // 添加新角色
        List<Integer> idList = roleDTO.getIdList();
        if(idList != null && idList.size() != 0)
        roleMapper.saveRoleInfoByUserId(idList,userId);
    }

    @Override
    public List<User> getUserInfoListByName(String name) {
        if(name == null || name.length() == 0) return userMapper.selectByExample(null);
        UserExample userExample = new UserExample();
        UserExample.Criteria criteria = userExample.createCriteria();
        criteria.andNameLike("%"+name+"%");
        return userMapper.selectByExample(userExample);
    }

    @Override
    public UserDTO emailLogin(UserDTO user) {
        String userQqMailbox = user.getUserQqMailbox();
        String code = user.getCode();
        // 检查验证码
        boolean b = mailService.checkVCode(userQqMailbox, code);
        if(!b) throw new RuntimeException("验证码无效");
        // 检查邮箱是否已被注册，如果未被注册则自动注册
        User u = userMapper.getUserAndRoleInfoByMail(userQqMailbox);
        if(u == null) {
            // 自动注册邮箱，用户名为UUID
            String s = UUID.randomUUID().toString();
            User user1 = new User(null, s, null, LocalDateTime.now(), null, userQqMailbox);
            userMapper.insertSelective(user1);
        }
        UsernamePasswordAuthenticationToken authenticationToken;
        authenticationToken = new UsernamePasswordAuthenticationToken(
                Constant.USER_QQ_MAILBOX_V_CODE + userQqMailbox, code);
        // 认证
        Authentication authenticate = null;
        try{
            authenticate = authenticationManager.authenticate(authenticationToken);
        }catch (AuthenticationException e){
            e.printStackTrace();
            throw new RuntimeException("认证失败");
        }
        if (Objects.isNull(authenticate)) {
            throw new RuntimeException("用户名或密码错误");
        }
        // 认证成功
        log.info("验证成功，开始清除验证码");
        redisTemplate.delete(Constant.VERIFICATION_CODE+userQqMailbox);
        log.info("清除完毕");
        // 使用userid生成token
        SecurityUser securityUser = (SecurityUser) authenticate.getPrincipal();
        User user1 = securityUser.getUser();
        String userId = user1.getId().toString();
        String jwt = JwtUtil.createJWT(userId);
        // 将用户信息SecurityUser存入redis中
        String key = "login:" + userId;
        redisTemplate.opsForValue().set(key, securityUser,1, TimeUnit.DAYS);
        UserDTO res = new UserDTO(user1.getId(), user1.getUsername(), null, null, jwt, user1.getUserAvatar(), user1.getUserQqMailbox());
        //把token响应给前端
        return res;

    }

    @Override
    public String updateAvatar(MultipartFile avatar) throws IOException {
        UUID uuid = UUID.randomUUID();
        String fileName = uuid.toString();
        if(avatar != null){
            if(!avatar.isEmpty()){
                String originalFilename = avatar.getOriginalFilename();
                String[] split = originalFilename.split("\\.");
                fileName = fileName+"."+split[1];
                // 上传到阿里云OSS
                AliYunOssUtil.upload(avatar.getBytes(), fileName);
                // 删除旧头像
                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();
                User user = securityUser.getUser();
                String userAvatar = user.getUserAvatar();
                Integer userId = user.getId();
                // 更新头像信息
                user.setUserAvatar(fileName);
                String key = Constant.LOGIN + user.getId();
                try{
                    String finalFileName = fileName;
                    CompletableFuture.runAsync(()->{
                        // 更新用户信息到redis   ,Mysql
                        redisTemplate.opsForValue().set(key, securityUser,1, TimeUnit.DAYS);
                        userMapper.updateAvatar(finalFileName,userId);
                        AliYunOssUtil.DeleteOne(userAvatar);
                    }, Executors.newFixedThreadPool(3));
                }  catch (Exception e){
                    e.printStackTrace();
                }

            }
        }
        return fileName;
    }

    @Override
    public void updateUsername(String username) {
        // 检查用户名是否合法
        boolean b = checkUsername(username);
        if(!b)  throw new RuntimeException(
                "不能包含" + Constant.USERNAME + "或者" + Constant.USER_QQ_MAILBOX + "字眼");
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();
        User user = securityUser.getUser();
        try {
            userMapper.updateUsername(username,user.getId());
        }catch (Exception e){
            e.printStackTrace();
            throw new RuntimeException("用户名已被占用");
        }
        // 修改redis中保存的用户数据
        user.setUsername(username);
        try{
            CompletableFuture.runAsync(()->{
                String key = Constant.LOGIN + user.getId();
                // 更新用户信息到redis
                redisTemplate.opsForValue().set(key, securityUser,1, TimeUnit.DAYS);
            }, Executors.newFixedThreadPool(3));
        }  catch (Exception e){
            e.printStackTrace();
        }
    }

    @Override
    public void updatePassword(String newPassword, String oldPassword) {
        // 校正旧密码
        if(oldPassword == null || oldPassword.length() ==0) oldPassword =null;
        // 查询出当前用户信息
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();
        User user = securityUser.getUser();
        Integer userId = user.getId();
        // 检查旧密码是否正确，放在登录使用的是邮箱验证码方式，所以我们要查询数据库的密码
        User user1 = userMapper.selectByPrimaryKey(userId);
        String password = user1.getPassword();
        if(password == null){
            if(oldPassword != null)
                throw new RuntimeException("旧密码错误");
        }else{
            if(oldPassword == null) throw new RuntimeException("旧密码错误");
            boolean matches = bCryptPasswordEncoder.matches(oldPassword, password);
            if(!matches) throw new RuntimeException("旧密码错误");
        }
        // 正确逻辑
        String encode = bCryptPasswordEncoder.encode(newPassword);
        // 更新数据库
        userMapper.updatePassword(encode,userId);
        // 更新redis
        user.setPassword(encode);
        CompletableFuture.runAsync(()->{
            String key = Constant.LOGIN + user.getId();
            // 更新用户信息到redis
            redisTemplate.opsForValue().set(key, securityUser,1, TimeUnit.DAYS);
        }, Executors.newFixedThreadPool(3));
    }

    @Override
    public void getForgetVerificationCode(String mail1) {
        mailService.sendMimeMail(mail1);
    }

    @Override
    public void forgetPassword(String newPassword, String mail, String code) {
        boolean b = mailService.checkVCode(mail, code);
        if(!b) throw new RuntimeException("验证码无效");
        // 有效逻辑
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();
        User user = securityUser.getUser();
        // 删除redis缓存的验证码
        redisTemplate.delete(Constant.VERIFICATION_CODE + mail);
        // 加密新密码
        newPassword = bCryptPasswordEncoder.encode(newPassword);
        user.setPassword(newPassword);
        // 更新redis
        String finalNewPassword = newPassword;
        CompletableFuture.runAsync(()->{
            // 更新mysql
            userMapper.updatePassword(finalNewPassword,user.getId());
            String key = Constant.LOGIN + user.getId();
            // 更新用户信息到redis
            redisTemplate.opsForValue().set(key, securityUser,1, TimeUnit.DAYS);
        }, Executors.newFixedThreadPool(3));

    }

    private boolean checkUsername(String username){
        if(username == null || username.length() == 0) return false;
        // 检查用户名是否包含不合法字符
        if((username.contains(Constant.USERNAME) ||
                username.contains(Constant.USER_QQ_MAILBOX)))
            return false;
        return true;
    }
}
